package asteroids.engine;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import org.apache.log4j.Logger;

import asteroids.entity.Asteroid;
import asteroids.entity.Ship;
import asteroids.entity.weapon.Bullet;

/**
 * Holds the complete state of the game at a single instance in time.
 * 
 * @author whitecd1
 * 
 */
public class GameState
{
    private static Logger logger = Logger.getLogger(GameState.class);

    private ObjectManager<Asteroid> asteroids;
    private ShipManager ships;
    private Scenario scenario;
    private BufferedImage gameImage;
    private Graphics gameGraphics;

    private long elapsedGameTime;

    @Deprecated
    // we use the Scenario to initialize all of that now
    public GameState(AsteroidManager asteroids, ShipManager ships, Scenario scenario)
    {
        this.asteroids = asteroids;
        this.ships = ships;
        this.scenario = scenario;
        gameImage = new BufferedImage(Utils.GAME_WIDTH, Utils.GAME_HEIGHT,
                BufferedImage.TYPE_INT_RGB);
        gameGraphics = gameImage.getGraphics();
        elapsedGameTime = 100L;
    }

    public GameState(Scenario scenario)
    {
        asteroids = new AsteroidManager();
        ships = new ShipManager();
        this.scenario = scenario;
        scenario.initialize(this);
        gameImage = new BufferedImage(Utils.GAME_WIDTH, Utils.GAME_HEIGHT,
                BufferedImage.TYPE_INT_RGB);
        gameGraphics = gameImage.getGraphics();
        elapsedGameTime = 100L;
    }

    public long currentGameTime()
    {
        return elapsedGameTime;
    }

    /**
     * Returns a GameState corresponding to a *probable next state, given the
     * current configuration
     * 
     * *probable because things like asteroid fragmentation are governed by
     * random forces, and cannot be predicted with certainty.
     * 
     * @return
     */
    public GameState getNextState()
    {
        // TODO
        /*
         * make a copy of self update copy return copy ensure copy shares NO
         * elements in common with this - i.e., changing copy cannot affect this
         * 
         * Perhaps it would be better to have a clone() method, and then just
         * update the clone normally?
         */
        return null;
    }

    public ObjectManager<Asteroid> getAsteroidManager()
    {
        return asteroids;
    }

    public ShipManager getShipManager()
    {
        return ships;
    }

    public void update()
    {
        logger.trace("GameState.update()");
        asteroids.update();
        ships.update(this);

        // gather all bullets
        ArrayList<Bullet> bullets = new ArrayList<Bullet>();
        for( Ship s : ships.getObjects() )
        {
            bullets.addAll(s.getObjects());
        }

        // check collisions in a sensible order
        for( Asteroid a : asteroids.getObjects() )
        {
            // asteroids and bullets
            for( Bullet b : bullets )
            {
                if( a.isAlive() && b.isAlive() && Utils.gameObjectsCollide(a, b) )
                {
                    logger.trace("colliding Asteroid with " + b);
                    double ap = a.getCollisionPower(b);
                    double bp = b.getCollisionPower(a);
                    a.collide(b, bp);
                    b.collide(a, ap);
                }
            }

            // ships and asteroids
            for( Ship s : ships.getObjects() )
            {
                if( s.isAlive() && a.isAlive() && Utils.gameObjectsCollide(s, a) )
                {
                    logger.trace("colliding ship with asteroid");
                    double ap = a.getCollisionPower(s);
                    double sp = s.getCollisionPower(a);
                    a.collide(s, sp);
                    s.collide(a, ap);
                }
            }
        }

        // bullets and bullets
        for( Bullet b1 : bullets )
        {
            for( Bullet b2 : bullets )
            {
                if( b1.isAlive() && b2.isAlive() && b1 != b2 && Utils.gameObjectsCollide(b1, b2) )
                {
                    logger.trace("Colliding " + b1 + " and " + b2);
                    double b1p = b1.getCollisionPower(b2);
                    double b2p = b2.getCollisionPower(b1);
                    b1.collide(b2, b2p);
                    b2.collide(b1, b1p);
                }
            }
        }

        for( Ship s : ships.getObjects() )
        {
            // ships and bullets
            for( Bullet b : bullets )
            {
                if( s.isAlive() && b.isAlive() && Utils.gameObjectsCollide(s, b) )
                {
                    logger.trace("colliding " + s + " with bullet");
                    double bp = b.getCollisionPower(s);
                    double sp = s.getCollisionPower(b);
                    s.collide(b, bp);
                    b.collide(s, sp);
                }
            }

            // ships and ships
            for( Ship o : ships.getObjects() )
            {
                if( s.isAlive() && o.isAlive() && !s.getAffiliation().equals(o.getAffiliation())
                        && Utils.gameObjectsCollide(s, o) )
                {
                    logger.trace("Colliding ship with ship");
                    double sp = s.getCollisionPower(o);
                    double op = o.getCollisionPower(s);
                    s.collide(o, op);
                    o.collide(s, sp);
                }
            }
        }

        scenario.update(this);
        elapsedGameTime++;
    }

    public void clear()
    {
        asteroids.clear();
        ships.clear();
        gameImage = new BufferedImage(Utils.GAME_WIDTH, Utils.GAME_HEIGHT,
                BufferedImage.TYPE_INT_RGB);
        gameGraphics = gameImage.getGraphics();
    }

    public BufferedImage redraw()
    {
        erase(gameGraphics);
        draw(gameGraphics);
        return gameImage;
    }

    public void draw(Graphics g)
    {
        ships.draw(g);
        asteroids.draw(g);
        scenario.draw(g);
    }

    public void erase(Graphics g)
    {
        /*
         * so, given that minimizing the game while in progress can cause
         * graphical issues, I'm inclined to go with the screen-erase rather
         * than the GameObject erase, though that was an interesting thing to
         * implement.
         */
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, Utils.GAME_WIDTH, Utils.GAME_HEIGHT);
        // ships.erase(g);
        // asteroids.erase(g);
        // scenario.erase(g);
    }
}
